package searchXML;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import es.deusto.ingenieria.is.csp.formulation.Constraint;
import es.deusto.ingenieria.is.csp.formulation.Variable;
import formulation.Alldiff;
import formulation.LeadDigitConstraint;
import formulation.Letra;
import formulation.Llevada;
import formulation.SumConstraint;

public class CriptosumaXMLParser extends DefaultHandler
{
	private int operandMaxLength;
	private int numOperands;
	private String result;
	
	private List<String> operands = new ArrayList<String>();
	private List<Constraint<Integer>> constraints = new ArrayList<Constraint<Integer>>();
	private List<Variable<Integer>> variables = new ArrayList<Variable<Integer>>();

	private List<ArrayList<Variable<Integer>>> operation = new ArrayList<ArrayList<Variable<Integer>>>();

	public CriptosumaXMLParser(String fileXML)  
	{				
		try 
		{
			if (fileXML != null && new File(fileXML).exists()) 
			{
				this.processXML(fileXML);
			} 
			else 
			{
				System.err.println("XML file name is 'null' or the file doesn't exists");
			}
		} 
		catch (Exception ex) 
		{
			System.err.println("% StateXMLReader(): " + ex.getMessage());
		}
	}
	
	private void processXML(String fileXML)
	{	        
        try 
        {
			SAXParserFactory parserFactory = SAXParserFactory.newInstance();
			parserFactory.setValidating(true);
			parserFactory.setNamespaceAware(true);
			SAXParser parser = parserFactory.newSAXParser();
			parser.parse(fileXML, this);
		} 
        catch (Exception ex) 
        {
			System.err.println("% StateXMLReader.processXML(): " + ex);	
		}
        
        System.out.println("OperandMaxLength: " + operandMaxLength);
        System.out.println("NumOperands: " + numOperands);
        System.out.println("Operands: " + this.operands.toString());
        System.out.println("Result: " + result);
        
		createVariablesAndConstraints();
		associateConstraintsToVariables();
	}
	
	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
	{
		try
		{	
			if (qName.equalsIgnoreCase("is:puzzle")) 
			{
				this.operandMaxLength = Integer.parseInt(attributes.getValue("operandMaxLength"));
				this.numOperands = Integer.parseInt(attributes.getValue("operands"));
			} 
			else if (qName.equalsIgnoreCase("is:operand")) 
			{
				this.operands.add(attributes.getValue("word").toUpperCase());	
			} 
			else if (qName.equalsIgnoreCase("is:result")) 
			{
				this.result = attributes.getValue("word").toUpperCase();
			}
		} 
		catch (Exception ex) {
			System.out.println(this.getClass().getName() + ".startElement(): " + ex);
		}
	}
	
	private void createVariablesAndConstraints()
	{
		ArrayList<Variable<Integer>> variablesAlldiff = new ArrayList<Variable<Integer>>();
		
		for (int i = 0; i < result.length(); i++)
		{
			constraints.add(new SumConstraint("SumConstraints " + (i + 1), new ArrayList<Variable<Integer>>(), new ArrayList<Variable<Integer>>()));
			
			Letra letra = (Letra) letterToVar(result.charAt((result.length() - 1) - i));
			
			if (variables.contains(letra))
			{
				letra = (Letra) variables.get(variables.indexOf(letra));
			}
			else
			{
				variables.add(letra);
				variablesAlldiff.add(letra);
			}
			
			((SumConstraint) constraints.get(i)).getRight().add(letra);
			
			if (i == result.length() - 1)
			{
				constraints.add(new LeadDigitConstraint("LeadDigitConstraint", letra));
			}
			
			operation.add(new ArrayList<Variable<Integer>>());
			
			if (i < (result.length() - 1))
			{
				Llevada llevada = new Llevada("X" + (i + 1), createCarryDomain());
				
				if (variables.contains(llevada))
				{
					llevada = (Llevada) variables.get(variables.indexOf(llevada));
				}
				else
				{
					variables.add(llevada);
				}
				
				((SumConstraint) constraints.get(i)).getRight().add(llevada);
			}
		}
		
		boolean primerOperando = true, operandosIguales = true;
		
		for (String operando : this.operands)
		{
			for (int i = 0; i < operando.length(); i++)
			{
				Letra letra = (Letra) letterToVar(operando.charAt((operando.length() - 1) - i));
				
				if (i > 0 && primerOperando && (operando.length() == operandMaxLength))
				{	
					Llevada llevada = new Llevada("X" + i, createCarryDomain());
					
					if (variables.contains(llevada))
					{
						llevada = (Llevada) variables.get(variables.indexOf(llevada));
					}
					else
					{
						variables.add(llevada);
					}
					
					((SumConstraint) constraints.get(i)).getLeft().add(0, llevada);
					
					if ((i + 1) == operando.length() && !operandosIguales)
					{
						Llevada ultimaLlevada = new Llevada("X" + (i + 1), createCarryDomain());
						
						if (variables.contains(ultimaLlevada))
						{
							ultimaLlevada = (Llevada) variables.get(variables.indexOf(ultimaLlevada));
						}
						else
						{
							variables.add(ultimaLlevada);
						}
						
						((SumConstraint) constraints.get(i + 1)).getLeft().add(0, ultimaLlevada);
					}
				}
				else if((operando.length() != operandMaxLength) || (operando.length() != result.length()))
				{
					operandosIguales = false;
				}
				
				if (variables.contains(letra))
				{
					letra = (Letra) variables.get(variables.indexOf(letra));
				}
				else
				{
					variables.add(letra);
					variablesAlldiff.add(letra);
				}
				
				((SumConstraint) constraints.get(i)).getLeft().add(letra);
			}
			
			if (operando.length() == operandMaxLength)
			{
				primerOperando = false;
			}
		}
		
//		System.out.println("Variables: " + variables.toString());
//		System.out.println("VariablesAllDiff: " + variablesAlldiff.toString());
		
		constraints.add(new Alldiff("AllDiff", variablesAlldiff));
		
//		System.out.println("Constraints:");
//		System.out.println("-------------------------------");
//		
//		for (int i = 0; i < constraints.size() - 2; i++)
//		{
//			System.out.println(((SumConstraint) constraints.get(i)).getLeft().toString());
//			System.out.println(((SumConstraint) constraints.get(i)).getRight().toString());
//			System.out.println("-------------------------------");
//		}
		
		for (int i = 0; i < constraints.size() - 2; i++)
		{
			((SumConstraint) constraints.get(i)).bindVariables();
			operation.get(i).addAll((ArrayList<Variable<Integer>>) ((SumConstraint) constraints.get(i)).getLeft());
			operation.get(i).addAll((ArrayList<Variable<Integer>>) ((SumConstraint) constraints.get(i)).getRight());
		}
	}
	
	private void associateConstraintsToVariables()
	{		
		for (Variable<Integer> variable : variables)
		{
			for (Constraint<Integer> constraint : constraints)
			{					
				for (Variable<Integer> variableRestriccion : constraint.getVariables())
				{
					if (variable.equals(variableRestriccion) && constraint instanceof Alldiff && variable.getName().length() == 1)
					{
						variable.addConstraint(constraint);
						break;
					}
					else if (variable.equals(variableRestriccion) && constraint instanceof SumConstraint)
					{
						variable.addConstraint(constraint);
						break;
					}
					else if (variable.equals(variableRestriccion) && constraint instanceof LeadDigitConstraint)
					{
						variable.addConstraint(constraint);
						break;
					}
				}
			}
		}
		
//		System.out.println("Restricciones de las variables: ");
//		
//		for (Variable<Integer> variable : variables)
//		{
//			System.out.println("Variable: " + variable.getName());
//			System.out.println(variable.getConstraints().toString());
//		}
	}
	
	private Variable<Integer> letterToVar(char letter)
	{
		return new Letra(String.valueOf(letter), this.createLetterDomain());
	}
	
	private List<Integer> createLetterDomain()
	{
		ArrayList<Integer> domain = new ArrayList<Integer>();
		
		for (int i = 1; i < 10; i++)
		{
			domain.add(i);
		}
		
		domain.add(0);
		
		return domain;
	}
	
	private List<Integer> createCarryDomain()
	{
		ArrayList<Integer> domain = new ArrayList<Integer>();
		
		for (int i = 1; i < numOperands; i++)
		{
			domain.add(i);
		}
		
		domain.add(0);
		
		return domain;
	}
	
	public List<Constraint<Integer>> getConstraints()
	{
		return constraints;
	}

	public void setConstraints(List<Constraint<Integer>> constraints)
	{
		this.constraints = constraints;
	}

	public List<Variable<Integer>> getVariables()
	{
		return variables;
	}

	public void setVariables(List<Variable<Integer>> variables)
	{
		this.variables = variables;
	}
	
	public List<ArrayList<Variable<Integer>>> getOperation()
	{
		return operation;
	}

	public void setOperation(List<ArrayList<Variable<Integer>>> operation)
	{
		this.operation = operation;
	}
	
	public int getOperandMaxLength()
	{
		return operandMaxLength;
	}

	public void setOperandMaxLength(int operandMaxLength)
	{
		this.operandMaxLength = operandMaxLength;
	}

	public String getResult()
	{
		return result;
	}

	public void setResult(String result)
	{
		this.result = result;
	}
}
